package com.xuan.friend.service.contest.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.xuan.common.core.constants.CacheConstants;
import com.xuan.common.core.constants.Constants;
import com.xuan.common.core.domain.TableDataInfo;
import com.xuan.common.core.enums.ContestZSetType;
import com.xuan.common.core.exception.BusinessException;
import com.xuan.common.core.utils.ThreadLocalUtils;
import com.xuan.common.redis.service.RedisService;
import com.xuan.friend.domain.contest.Contest;
import com.xuan.friend.domain.contest.dto.ContestQueryDTO;
import com.xuan.friend.domain.contest.dto.ContestRankDTO;
import com.xuan.friend.domain.contest.vo.ContestRankVo;
import com.xuan.friend.domain.contest.vo.ContestVo;
import com.xuan.friend.domain.user.vo.UserDetailVo;
import com.xuan.friend.manager.ContestCacheManager;
import com.xuan.friend.manager.UserCacheManager;
import com.xuan.friend.mapper.contest.ContestMapper;
import com.xuan.friend.mapper.user.UserContestMapper;
import com.xuan.friend.service.contest.ContestService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.time.ZonedDateTime;
import java.util.List;

/**
 * @author ASUS
 * @description 针对表【tb_contest(竞赛表)】的数据库操作Service实现
 * @createDate 2024-11-16 22:40:37
 */
@Service
@Slf4j
public class ContestServiceImpl extends ServiceImpl<ContestMapper, Contest>
        implements ContestService {

    @Resource
    private ContestMapper contestMapper;

    @Resource
    private UserContestMapper userContestMapper;

    @Resource
    private ContestCacheManager contestCacheManager;

    @Resource
    private RedisService redisService;

    @Resource
    private UserCacheManager userCacheManager;

    @Override
    public List<ContestVo> listContest(ContestQueryDTO contestQueryDTO) {
        Integer type = contestQueryDTO.getType();
        Integer pageNum = contestQueryDTO.getPageNum();
        Integer pageSize = contestQueryDTO.getPageSize();
        if (type == null || (type != 0 && type != 1)) {
            throw new BusinessException("要查看的竞赛类型不存在");
        }
        PageHelper.startPage(pageNum, pageSize);
        return contestMapper.listContest(contestQueryDTO);
    }

    @Override
    public TableDataInfo redisListContest(ContestQueryDTO contestQueryDTO) {
        // 如果按时间进行查询,检查参数是否合法并将时间转为时间戳
        checkAndConvertTime(contestQueryDTO);

        Integer type = contestQueryDTO.getType();
        Long total = contestCacheManager.getContestZSetSize(type, null, contestQueryDTO);
        List<ContestVo> contestVoList;
        if (total == null || total <= 0) {
            // redis 中没有缓存数据,从数据库中查询
            contestVoList = listContest(contestQueryDTO);
            contestCacheManager.refreshContestCache(type, null);     // 刷新数据到缓存中
            total = new PageInfo(contestVoList).getTotal();
        } else {
            // 从 redis 中查询对应的数据
            contestVoList = contestCacheManager.getContestVoList(contestQueryDTO, null);
            // 如果出现异常情况, 刷新缓存后 redis 中的数据会发生变化, 重新获取
            total = contestCacheManager.getContestZSetSize(type, null, contestQueryDTO);
        }
        // 设置是否加入该竞赛
        assembleContestVOList(contestVoList);
        return TableDataInfo.success(contestVoList, total);
    }

    @Override
    public TableDataInfo listRank(ContestRankDTO contestRankDTO) {
        Long contestId = contestRankDTO.getContestId();
        // 先从 redis 查询,不存在再从数据库查询
        Long total = contestCacheManager.getContestRankListSize(contestId);
        List<ContestRankVo> contestRankVoList;
        if (total == null || total <= 0) {
            // redis 中没有缓存数据,从数据库中查询
            PageHelper.startPage(contestRankDTO.getPageNum(), contestRankDTO.getPageSize());
            contestRankVoList = userContestMapper.listRank(contestId);
            contestCacheManager.refreshContestRankCache(contestId);     // 刷新数据到缓存中
            total = new PageInfo(contestRankVoList).getTotal();
        } else {
            // 从 redis 中查询对应的数据
            contestRankVoList = contestCacheManager.getContestRankVoList(contestRankDTO);
            // 如果出现异常情况, 刷新缓存后 redis 中的数据会发生变化, 重新获取
            total = contestCacheManager.getContestRankListSize(contestId);
        }
        // 将 userId 转换为 nickName
        // 此处存储 userId 的原因是用户有可能更改名称
        assembleContestRank(contestRankVoList);

        if (CollectionUtils.isEmpty(contestRankVoList)) {
            return TableDataInfo.empty();
        }
        return TableDataInfo.success(contestRankVoList, total);

    }

    private void assembleContestRank(List<ContestRankVo> contestRankVoList) {
        // 将 userId 转换为 nickName
        for (ContestRankVo contestRankVo : contestRankVoList) {
            UserDetailVo userDetailVo = userCacheManager.getUserById(contestRankVo.getUserId());
            contestRankVo.setNickName(userDetailVo.getNickName());
        }
    }


    @Override
    public String getFirstQuestionId(Long contestId) {
        checkAndRefresh(contestId);
        return contestCacheManager.getContestFirstQuestionId(contestId).toString();
    }


    @Override
    public String prevQuestion(Long questionId, Long contestId) {
        checkAndRefresh(contestId);
        return contestCacheManager.prevQuestion(questionId, contestId).toString();
    }

    @Override
    public String nextQuestion(Long questionId, Long contestId) {
        checkAndRefresh(contestId);
        return contestCacheManager.nextQuestion(questionId, contestId).toString();
    }


    /**
     * 设置用户是否报名该竞赛
     */
    private void assembleContestVOList(List<ContestVo> contestVoList) {
        Long userId = ThreadLocalUtils.get(Constants.USER_ID, Long.class);
        if (userId == null) {
            return;
        }
        String userContestZSetKey = CacheConstants.USER_CONTEST_ZSET_KEY + userId;
        List<Long> userContestIdList = redisService.reverseRangeForZSet(userContestZSetKey, 0, -1, Long.class);
        if (CollectionUtils.isEmpty(userContestIdList)) {
            // 用户竞赛列表,可能过期了
            contestCacheManager.refreshContestCache(ContestZSetType.USER_CONTEST.getValue(), userId);
            userContestIdList = redisService.getCacheListByRange(userContestZSetKey, 0, -1, Long.class);
        }
        if (CollectionUtils.isEmpty(userContestIdList)) {
            return;
        }

        // 设置是否加入该竞赛
        for (ContestVo contestVo : contestVoList) {
            if (userContestIdList.contains(contestVo.getContestId())) {
                contestVo.setEnter(true);
            }
        }
    }

    private void checkAndRefresh(Long contestId) {
        // 先从 redis 中查询, redis 没有数据则刷新缓存
        Long listSize = contestCacheManager.getContestQuestionListSize(contestId);
        if (listSize == null || listSize <= 0) {
            contestCacheManager.refreshContestQuestionCache(contestId);
        }
    }


    private void checkAndConvertTime(ContestQueryDTO contestQueryDTO) {
        String startTime = contestQueryDTO.getStartTime();
        String endTime = contestQueryDTO.getEndTime();
        if (StringUtils.isAllBlank(startTime, endTime)) {
            return;
        }
        if (StringUtils.isAnyBlank(startTime, endTime)) {
            throw new BusinessException("请指定开始时间和结束时间");
        }
        double startEpochSecond = (double) ZonedDateTime.parse(startTime).toEpochSecond();
        double endEpochSecond = (double) ZonedDateTime.parse(endTime).toEpochSecond();
        if (startEpochSecond > endEpochSecond) {
            throw new BusinessException("开始时间必须早于结束时间");
        }
        contestQueryDTO.setStartEpochSecond(startEpochSecond);
        contestQueryDTO.setEndEpochSecond(endEpochSecond);
    }
}




